
import { IMatcher } from "../tsgfServer/match/IMatcher";
import { MatcherRequests } from "../tsgfServer/match/MatcherRequests";
import { EMatchProcType, IMatcherExecResult, IMatchFromRoomJoinUsOnServer, IMatchProc, IMatchRequest } from "../tsgfServer/match/Models";
import { MatchRequestHelper } from "../tsgfServer/match/MatchRequestHelper";
import { IResult, Result } from "../tsgf/Result";
import { EMatchFromType, IMatchResult } from "../tsgf/match/Models";
import { ERoomRegChangedType, IRoomRegChanged, RoomHelper } from "../tsgfServer/room/RoomHelper";
import { IGameServerInfo } from "../hallClient/Models";
import { arrRemoveItems } from "../tsgf/Utils";
import { logger } from "../tsgf/logger";
import { ERoomCreateType } from "../tsgf/room/IRoomInfo";
import { IRoomGameServerRegInfo } from "../tsgfServer/room/Models";
import { buildGuid } from "../tsgfServer/ServerUtils";

/**应用匹配请求管理*/
export class AppMatchRequestMgr {

    public appId: string;
    /**本应用的所有匹配器，按匹配器标识字典*/
    public matchers: Map<string, IMatcher> = new Map<string, IMatcher>();
    /**本应用的所有匹配器下的匹配请求*/
    public allMatcherReqs: Map<string, MatcherRequests> = new Map<string, MatcherRequests>();
    /**本应用的所有匹配请求*/
    public allReqs: Map<string, IMatchRequest> = new Map<string, IMatchRequest>();
    /**公共的请求处理工具*/
    public reqHelper: MatchRequestHelper;

    /**本应用下的房间注册信息缓存, 由父级服务进行维护*/
    public roomRegInfos: Map<string, IRoomGameServerRegInfo> = new Map<string, IRoomGameServerRegInfo>();
    /**房间招人的匹配,一个房间只能存在一个匹配请求*/
    public roomJoinUsReq: Map<string, IMatchRequest> = new Map<string, IMatchRequest>();

    public allotGameServer: () => Promise<IGameServerInfo | null>;

    protected pollMatchReqHd: any = null;
    protected pollProcTimeoutMatchReqHd: any = null;

    /**
     * @date 2022/5/5 - 16:17:09
     *
     * @constructor
     * @param {string} appId
     * @param {IQueue} appMatchReqQueue 本应用的匹配请求队列
     * @param {MatchRequestHelper} reqHelper 公共的请求工具类
     * @param {() => Promise<IGameServerInfo>} allotGameServer 设置一个分配游戏服务器的方法
     */
    constructor(appId: string, reqHelper: MatchRequestHelper, allotGameServer: () => Promise<IGameServerInfo | null>) {
        this.appId = appId;
        this.reqHelper = reqHelper;
        this.reqHelper.listenMatchProc(this.appId, async (proc) => {
            await this.onNewAppMatchProc(proc);
        });
        this.allotGameServer = allotGameServer;
        this.startPollProcTimeoutReqs();
    }
    /**
     * 清除数据
     * @date 2022/5/5 - 16:08:46
     *
     * @public
     */
    public dispose() {
        this.reqHelper.stopListenMatchProc(this.appId);
        this.stopPollReqs();
        this.startPollProcTimeoutReqs();
    }

    public roomRegInfoChanged(regRoomChanged: IRoomRegChanged) {
        let req = this.roomJoinUsReq.get(regRoomChanged.regInfo.roomId);
        switch (regRoomChanged.changedType) {
            case ERoomRegChangedType.Create:
                this.roomRegInfos.set(regRoomChanged.regInfo.roomId, regRoomChanged.regInfo);
                break;
            case ERoomRegChangedType.Delete:
                this.roomRegInfos.delete(regRoomChanged.regInfo.roomId);
                if (req) {
                    //如果房间存在招人匹配,则同步删除匹配!
                    this.removeMatchRequest(req);
                }
                break;
            case ERoomRegChangedType.PlayerJoinRoom:
                if (req) {
                    let fromInfo = req.matchFromInfo as IMatchFromRoomJoinUsOnServer;
                    //房间招人匹配的当前人数 = 应匹配进入的玩家人数 + 非匹配进入玩家人数
                    let forecastIds = new Set(fromInfo.matchPlayerIds.concat(regRoomChanged.regInfo.currPlayerIds));
                    fromInfo.currPlayerCount = forecastIds.size;
                }
                break;
            case ERoomRegChangedType.PlayerLeaveRoom:
                if (req) {
                    let fromInfo = req.matchFromInfo as IMatchFromRoomJoinUsOnServer;
                    //离开的玩家,从匹配玩家id中移除
                    arrRemoveItems(fromInfo.matchPlayerIds, p => p === regRoomChanged.leaveRoomPlayerId);
                    //房间招人匹配的当前人数 = 应匹配进入的玩家人数 + 非匹配进入玩家人数
                    let forecastIds = new Set(fromInfo.matchPlayerIds.concat(regRoomChanged.regInfo.currPlayerIds));
                    fromInfo.currPlayerCount = forecastIds.size;
                }
                break;
        }
    }
    /**尝试获取或者拉取房间注册信息,返回null表示房间已经被删除(跨服务器)*/
    public async getRoomRegInfo(roomId: string): Promise<IRoomGameServerRegInfo | null> {
        let roomRegInfo = this.roomRegInfos.get(roomId);
        if (!roomRegInfo) {
            let tmp = await RoomHelper.getRoomRegInfo(roomId);
            if (!tmp) {
                return null;
            }
            roomRegInfo = tmp;
            this.roomRegInfos.set(roomId, roomRegInfo);
        }
        return roomRegInfo;
    }

    /**停止定时轮询请求*/
    protected stopPollReqs() {
        if (this.pollMatchReqHd) clearTimeout(this.pollMatchReqHd);
        this.pollMatchReqHd = null;
    }
    /**开始轮询所有请求*/
    protected startPollAllReqs() {
        this.stopPollReqs();
        this.pollMatchReqHd = setTimeout(async () => await this.pollAllReqs(), 1000);
    }
    /**执行轮询所有请求（给匹配器执行自己匹配器下的请求集合）*/
    protected async pollAllReqs(): Promise<void> {
        //处理一遍超时的
        await this.pollProcTimeoutReqs();

        if (this.allMatcherReqs.size > 0) {
            for (let [matcherKey, matcherReqs] of this.allMatcherReqs) {
                let matcher = this.matchers.get(matcherKey);
                if (!matcher || matcherReqs.length <= 0) continue;
                logger.log('AppMatchRequestMgr', `pollAllReqs:${matcher.matcherKey},matcherReqsCount:${matcherReqs.length}`);
                let result = matcher.onPollMatcherReqs(matcherReqs.slice());
                await this.procMatcherExecResult(result, matcherReqs);
            }
        }
        //重新开始定时轮询
        this.startPollAllReqs();
    }

    /**新匹配请求添加进本管理器*/
    protected async addNewMatchReq(matchReq: IMatchRequest): Promise<MatcherRequests> {
        //更新请求相关字段
        matchReq.startMatchTime = Date.now();
        //添加到所有请求集合中
        this.allReqs.set(matchReq.matchReqId, matchReq);
        //添加到同匹配器下的请求集合中
        let matcherAllReqs = this.allMatcherReqs.get(matchReq.matcherKey);
        if (!matcherAllReqs) {
            matcherAllReqs = new MatcherRequests(matchReq.matcherKey);
            this.allMatcherReqs.set(matcherAllReqs.matcherKey, matcherAllReqs);
        }
        matcherAllReqs.push(matchReq);

        if (matchReq.matchFromType === EMatchFromType.RoomJoinUs) {
            //如果是房间招人匹配,特殊处理一下
            let existsJoinUsReq = this.roomJoinUsReq.get(matchReq.matchFromInfo.roomId);
            if (existsJoinUsReq) {
                //居然存在同房间还有其他的招人匹配!正常不应该,但出现了就设置为失败
                await this.faildMatchRequest(existsJoinUsReq, `被其他房间招人匹配覆盖！`, 2102, matcherAllReqs);
            }
            if (!matchReq.matchFromInfo.matchPlayerIds) matchReq.matchFromInfo.matchPlayerIds = [];
            //放在房间招人字典里,统一管理
            this.roomJoinUsReq.set(matchReq.matchFromInfo.roomId, matchReq);
        }

        return matcherAllReqs;
    }

    protected stopPollProcTimeoutReqs() {
        if (this.pollProcTimeoutMatchReqHd) clearTimeout(this.pollProcTimeoutMatchReqHd);
        this.pollProcTimeoutMatchReqHd = null;
    }
    protected startPollProcTimeoutReqs() {
        this.stopPollProcTimeoutReqs();
        this.pollProcTimeoutMatchReqHd = setTimeout(async () => await this.pollProcTimeoutReqs(), 1000);
    }
    /**处理超时的匹配(设置结果并移出管理)*/
    protected async pollProcTimeoutReqs(): Promise<void> {
        if (this.allMatcherReqs.size > 0) {
            let now = Date.now();
            let timeoutReqIds: IMatchRequest[] = [];
            for (let matcherKey of this.allMatcherReqs.keys()) {
                let matcherReqs = this.allMatcherReqs.get(matcherKey);
                let matcher = this.matchers.get(matcherKey);
                if (!matcher || !matcherReqs || matcherReqs.length <= 0) continue;
                for (let i = 0; i < matcherReqs.length; i++) {
                    let req = matcherReqs[i];
                    if (!req.matchTimeoutSec) continue;
                    if (req.startMatchTime + req.matchTimeoutSec * 1000 < now) {
                        //这个请求已经超时
                        timeoutReqIds.push(req);
                    }
                }
            }
            if (timeoutReqIds.length > 0) {
                await this.faildMatchRequests(timeoutReqIds, '匹配超时！', 1003);
            }
        }

        //重新开始定时轮询
        this.startPollProcTimeoutReqs();
    }

    /**当收到新匹配请求的处理*/
    protected async onNewAppMatchProc(matchProc: IMatchProc) {
        if (matchProc.procType === EMatchProcType.RequestMatch) {
            //收到新的匹配请求
            return await this.onNewAppMatchReq(matchProc.matchReqId);
        } else if (matchProc.procType === EMatchProcType.CancelMatch) {
            //收到取消匹配操作
            return await this.onCancelMatchReq(matchProc.matchReqId);
        }
    }
    protected async onNewAppMatchReq(matchReqId: string) {
        let matchReq = await this.reqHelper.getMatchRequest(this.appId, matchReqId);
        if (!matchReq) {
            //全局请求数据已经被删除,则忽略
            logger.warn('AppMatchRequestMgr', '匹配请求已经被删除!', this.appId, matchReqId);
            return;
        }
        let matcher = this.matchers.get(matchReq.matcherKey);
        if (!matcher) {
            //没实现的匹配器,设置匹配失败结果
            return await this.faildMatchRequest(matchReq, `没有对应的匹配器实现${matchReq.matcherKey}`, 2001);
        }
        let fromRoomJoinUsRegInfo: IRoomGameServerRegInfo | null = null;
        if (matchReq.matchFromType === EMatchFromType.RoomJoinUs) {
            //如果是房间招人匹配, 则需要提交房间id
            if (!matchReq.matchFromInfo.roomId) {
                return await this.faildMatchRequest(matchReq, `matchFromInfo.roomId不能为空！`, 2002);
            }
            fromRoomJoinUsRegInfo = await this.getRoomRegInfo(matchReq.matchFromInfo.roomId);
            if (!fromRoomJoinUsRegInfo) {
                //房间已经被解散, 则设置失败直接返回
                return await this.faildMatchRequest(matchReq, `房间已经解散！`, 2003);
            }
        }

        //验证都通过了,加入匹配请求!
        let matcherAllReqs = await this.addNewMatchReq(matchReq);

        //匹配器执行得到结果
        logger.log('AppMatchRequestMgr', `onNewMatchReq:${matcher.matcherKey},matcherReqsCount:${matcherAllReqs.length}`);
        let ret = matcher.onNewMatchReq(matchReq, matcherAllReqs.slice());
        await this.procMatcherExecResult(ret, matcherAllReqs, matchReq);

        //重新开始定时轮询
        this.startPollAllReqs();

    }
    protected async onCancelMatchReq(matchReqId: string) {
        let matchReq: IMatchRequest | undefined | null = this.allReqs.get(matchReqId);
        if (!matchReq) {
            //如果不在本地，则尝试读取redis中的
            matchReq = await this.reqHelper.getMatchRequest(this.appId, matchReqId);
            if (!matchReq) {
                //还是没有，则忽略掉，当作已经完成取消！
                return;
            }
        }
        let matcherReqs = this.allMatcherReqs.get(matchReq.matcherKey);
        if (!matcherReqs) {
            matcherReqs = new MatcherRequests(matchReq.matcherKey);
            this.allMatcherReqs.set(matcherReqs.matcherKey, matcherReqs);
        }
        //移除匹配请求相关所有数据
        this.removeMatchRequest(matchReq, matcherReqs);
    }



    /**
     * 处理匹配器执行结果
     * @date 2022/5/12 - 15:23:59
     *
     * @protected
     * @async
     * @param {IMatcherExecResult} result
     * @param {?MatcherRequests} [matcherAllReqs] 匹配器下的所有匹配请求, 只有在新的匹配请求且没对应匹配器时，才没得传！
     * @param {?IMatchRequest} [currReq] 针对单个匹配请求触发时
     * @returns {Promise<void>}
     */
    protected async procMatcherExecResult(result: IMatcherExecResult, matcherAllReqs?: MatcherRequests, currReq?: IMatchRequest): Promise<void> {
        if (result.resultErrCode || result.resultErrMsg) {
            result.resultErrMsg = result.resultErrMsg ?? '匹配失败';
            result.resultErrCode = result.resultErrCode ?? 2000;
            //有错误，并且有指定请求，才设置当前请求为失败
            if (currReq) {
                this.faildMatchRequest(currReq, result.resultErrMsg, result.resultErrCode);
                return;
            }
            if (matcherAllReqs) {
                for (let req of matcherAllReqs) {
                    this.faildMatchRequest(req, result.resultErrMsg, result.resultErrCode);
                }
                return;
            }
            return;
        }
        if (!result.hasResult) {
            //没结果直接返回
            return;
        }

        if (!matcherAllReqs) {
            //正常这里要传当前匹配器下的所有请求,如果没有
            logger.error(`AppMatchRequestMgr.procMatcherExecResult 都有结果了，还没传匹配器下所有匹配请求！`);
            return;
        }

        if (result.resultCreateRoom) {
            //匹配结果有创建房间
            for (let createRoomResult of result.resultCreateRoom) {
                let resultReqs = matcherAllReqs.filter(r => createRoomResult.matchReqIds.includes(r.matchReqId));
                let gameServer = await this.allotGameServer();
                if (!gameServer) {
                    //当前没游戏服务器可用了！
                    await this.faildMatchRequests(resultReqs, '游戏服务器爆满，请稍后再试！', 1002);
                    continue;
                }

                let createInfo = RoomHelper.buildRoomInfo(this.appId, gameServer.serverNodeId, createRoomResult.createRoomPara);
                createInfo.roomInfo.createType = ERoomCreateType.MATCH_CREATE;
                await RoomHelper.regRoom(createInfo.regInfo, createInfo.roomInfo);
                let matchResult: IMatchResult = {
                    roomId: createInfo.roomInfo.roomId,
                    gameServerUrl: gameServer.serverUrl,
                };
                await this.succMatchRequests(resultReqs, matchResult, matcherAllReqs);

                if (createRoomResult.roomJoinUsMatch) {
                    //创建好的房间,还需要继续招人匹配,则构建匹配请求数据,并加入本管理(将在下个轮询或者下个新匹配时触发)
                    let useReq = resultReqs[0];
                    let attr = Object.assign({}, useReq.matcherParams);
                    let matcherPlayerIds = createRoomResult.matchPlayerIds.slice();
                    await this.addNewMatchReq({
                        matchReqId: buildGuid('MatchReq_'),
                        requestTime: Date.now(),
                        startMatchTime: Date.now(),
                        matchTimeoutSec: useReq.matchTimeoutSec,
                        matcherKey: useReq.matcherKey,
                        maxPlayers: useReq.maxPlayers,
                        matcherParams: attr,
                        matchFromType: EMatchFromType.RoomJoinUs,
                        matchFromInfo: {
                            roomId: createInfo.roomInfo.roomId,
                            currPlayerCount: createRoomResult.matchPlayerIds.length,
                            matchPlayerIds: matcherPlayerIds,
                        },
                    });
                }
            }
        }

        if (result.resultJoinRoom) {
            //匹配结果有加入房间
            for (let joinRoomResult of result.resultJoinRoom) {
                let resultReqs = matcherAllReqs.filter(r => joinRoomResult.matchReqIds.includes(r.matchReqId));
                let gameServer = await this.allotGameServer();
                if (!gameServer) {
                    //当前没游戏服务器可用了！
                    await this.faildMatchRequests(resultReqs, '游戏服务器爆满，请稍后再试！', 1002);
                    continue;
                }

                let matchResult: IMatchResult = {
                    roomId: joinRoomResult.joinRoomId,
                    gameServerUrl: gameServer.serverUrl,
                };
                await this.succMatchRequests(resultReqs, matchResult, matcherAllReqs);

                let roomJoinUsMatch = this.roomJoinUsReq.get(joinRoomResult.joinRoomId);
                if (roomJoinUsMatch) {
                    let fromInfo = roomJoinUsMatch.matchFromInfo as IMatchFromRoomJoinUsOnServer;
                    //将匹配的玩家id列表加入到招人匹配的玩家列表中,用于下次计算
                    fromInfo.matchPlayerIds.push(...joinRoomResult.matchPlayerIds);
                }
                if (!joinRoomResult.roomJoinUsMatch) {
                    //加入的房间,要求停止招人匹配
                    if (roomJoinUsMatch) {
                        //该房间确实存在招人匹配,执行清除数据
                        this.removeMatchRequest(roomJoinUsMatch);
                    }
                }
            }
        }

    }

    /**
     * 设置匹配请求结果,并移除本地数据
     * @date 2022/5/23 - 22:09:59
     *
     * @protected
     * @async
     * @param {IMatchRequest} matchReq
     * @param {IResult<IMatchResult>} result
     * @param {?IMatchRequest[]} [matcherAllReqs] 如果有获取好的匹配器所有请求,则传入
     * @returns {Promise<boolean>}
     */
    protected async setMatchRequestResult(matchReq: IMatchRequest, result: IResult<IMatchResult>, matcherAllReqs?: IMatchRequest[]): Promise<boolean> {
        if (!matcherAllReqs) {
            matcherAllReqs = this.allMatcherReqs.get(matchReq.matcherKey);
        }
        if (matcherAllReqs) {
            //匹配器请求中移除
            arrRemoveItems(matcherAllReqs, r => r.matchReqId === matchReq.matchReqId);
        }
        //从所有请求中移除
        this.allReqs.delete(matchReq.matchReqId);
        //如果是房间招人的匹配,则同步删除房间招人匹配列表
        if (matchReq.matchFromType === EMatchFromType.RoomJoinUs) {
            this.roomJoinUsReq.delete(matchReq.matchFromInfo.roomId);
        }
        //设置redis中的匹配结果
        return await this.reqHelper.setMatchRequestResult(this.appId, matchReq, result);
    }

    /**
     * 移除匹配请求数据(本地和redis), 只有在不需要结果的情况下,才直接删除(比如已经拿到结果 或者 主动取消的情况)
     * @date 2022/5/23 - 22:10:49
     *
     * @protected
     * @async
     * @param {IMatchRequest} matchReq
     * @param {?IMatchRequest[]} [matcherAllReqs] 如果有获取好的匹配器所有请求,则传入
     * @returns {Promise<void>}
     */
    protected async removeMatchRequest(matchReq: IMatchRequest, matcherAllReqs?: IMatchRequest[]): Promise<void> {
        if (!matcherAllReqs) {
            matcherAllReqs = this.allMatcherReqs.get(matchReq.matcherKey);
        }
        if (matcherAllReqs) {
            //匹配器请求中移除
            arrRemoveItems(matcherAllReqs, r => r.matchReqId === matchReq.matchReqId);
        }
        //从所有请求中移除
        this.allReqs.delete(matchReq.matchReqId);
        //如果是房间招人的匹配,则同步删除房间招人匹配列表
        if (matchReq.matchFromType === EMatchFromType.RoomJoinUs) {
            this.roomJoinUsReq.delete(matchReq.matchFromInfo.roomId);
        }
        //删除redis中的数据
        return await this.reqHelper.removeMatchRequest(this.appId, matchReq.matchReqId);
    }

    /**
     * 设置匹配请求为失败
     * @date 2022/5/11 - 18:06:34
     *
     * @public
     * @async
     * @param {IMatchRequest} matchReq
     * @param {string} errMsg
     * @param {number} errCode 错误码请看 IMatcherExecResult 的注释
     * @param {?IMatchRequest[]} [matcherAllReqs] 如果有获取好的匹配器所有请求,则传入
     * @returns {Promise<void>}
     */
    public async faildMatchRequest(matchReq: IMatchRequest, errMsg: string, errCode: number, matcherAllReqs?: IMatchRequest[]): Promise<void> {
        await this.setMatchRequestResult(matchReq, Result.buildErr(errMsg, errCode), matcherAllReqs);
    }
    /**
     * 设置多个匹配请求为失败
     * @date 2022/5/11 - 18:06:34
     *
     * @public
     * @async
     * @param {Array<IMatchRequest>} matcherReqs
     * @param {string} errMsg
     * @param {number} errCode 错误码请看 IMatcherExecResult 的注释
     * @param {?IMatchRequest[]} [matcherAllReqs] 如果有获取好的匹配器所有请求,则传入
     * @returns {Promise<void>}
     */
    public async faildMatchRequests(matcherReqs: Array<IMatchRequest>, errMsg: string, errCode: number, matcherAllReqs?: IMatchRequest[]): Promise<void> {
        for (let req of matcherReqs) {
            await this.setMatchRequestResult(req, Result.buildErr(errMsg, errCode), matcherAllReqs);
        }
    }
    /**
     * 设置匹配请求为成功
     * @date 2022/5/11 - 18:06:34
     *
     * @public
     * @async
     * @param {IMatchRequest} matchReq
     * @param {IMatchResult} result
     * @param {?IMatchRequest[]} [matcherAllReqs] 如果有获取好的匹配器所有请求,则传入
     * @returns {Promise<void>}
     */
    public async succMatchRequest(matchReq: IMatchRequest, result: IMatchResult, matcherAllReqs?: IMatchRequest[]): Promise<void> {
        await this.setMatchRequestResult(matchReq, Result.buildSucc(result), matcherAllReqs);
    }
    /**
     * 设置多个匹配请求为成功
     * @date 2022/5/11 - 18:06:34
     *
     * @public
     * @async
     * @param {Array<IMatchRequest>} matcherReqs
     * @param {IMatchResult} result
     * @param {?IMatchRequest[]} [matcherAllReqs] 如果有获取好的匹配器所有请求,则传入
     * @returns {Promise<void>}
     */
    public async succMatchRequests(matcherReqs: Array<IMatchRequest>, result: IMatchResult, matcherAllReqs?: IMatchRequest[]): Promise<void> {
        for (let req of matcherReqs) {
            await this.setMatchRequestResult(req, Result.buildSucc(result), matcherAllReqs);
        }
    }
}